/*
 * libSockets.c
 *
 *  Created on: 27/02/2012
 *      Author: utnso
 */

#include "libSockets.h"

//VARIABLES GLOBALES AL ARCHIVO
static t_nodoThread *pilaThreads = NULL;
static char* IP = NULL;
static int32_t puerto = 0;
static uint8_t maxConexiones = 0;
static t_conexion* poolConexiones = NULL;
pthread_mutex_t mutexUsandoPoolConexiones = PTHREAD_MUTEX_INITIALIZER;

void inicializarDatosSockets(char* varIP, int32_t varPuerto, uint8_t varMaxConexiones) {
	if (IP == NULL && puerto == 0 && maxConexiones == 0) {
		IP = (char*) malloc(strlen(varIP) + 1);
		memset(IP, '\0', strlen(varIP) + 1);
		strcpy(IP, varIP);

		puerto = varPuerto;

		maxConexiones = varMaxConexiones;
	}
}

void inicializarPoolConexiones() {

	pthread_mutex_lock(&mutexUsandoPoolConexiones);
	poolConexiones = (t_conexion*) malloc(maxConexiones * sizeof(t_conexion));

	uint8_t i;
	for (i = 0; i < maxConexiones; i++) {
		poolConexiones[i].descriptor = 0;
		poolConexiones[i].enUso = -1;
	}
	pthread_mutex_unlock(&mutexUsandoPoolConexiones);
}

void finalizarDatosSockets(void) {
	free(IP);
	IP = NULL;
	esperarThreads(&pilaThreads);
}

void finalizarPoolConexiones(void) {
	pthread_mutex_lock(&mutexUsandoPoolConexiones);
	uint8_t i;
	for (i = 0; i < maxConexiones; i++) {
		if (poolConexiones[i].enUso != -1) {
			close(poolConexiones[i].descriptor);
		}
	}
	free(poolConexiones);
	poolConexiones = NULL;
	pthread_mutex_unlock(&mutexUsandoPoolConexiones);
}

void obtenerConexion(int32_t* descriptor) {
	uint8_t encontrada = 0;
	while (!encontrada) {
		pthread_mutex_lock(&mutexUsandoPoolConexiones);
		if (poolConexiones != NULL) {
			uint8_t i;
			for (i = 0; i < maxConexiones && poolConexiones[i].enUso == 1; i++)
				;
			if (i < maxConexiones && poolConexiones[i].enUso == -1) {
				struct sockaddr_in my_addr;
				uint32_t hsockAux;
				uint32_t *p_int;
				hsockAux = socket(AF_INET, SOCK_STREAM, 0);
				if (hsockAux == -1) {
					printf("Error en el socket()\n");
					encontrada = 0;
				} else {
					p_int = (uint32_t*) malloc(sizeof(uint32_t));
					*p_int = 1;
					if ((setsockopt(hsockAux, SOL_SOCKET, SO_REUSEADDR, (char*) p_int, sizeof(uint32_t)) == -1) || (setsockopt(hsockAux, SOL_SOCKET, SO_KEEPALIVE, (char*) p_int, sizeof(uint32_t)) == -1)) {
						printf("Error en el setsockopt()\n");
						encontrada = 0;
					} else {
						free(p_int);
						my_addr.sin_family = AF_INET;
						my_addr.sin_port = htons(puerto);
						memset(&(my_addr.sin_zero), 0, 8);
						my_addr.sin_addr.s_addr = inet_addr(IP);
						if (connect(hsockAux, (struct sockaddr*) &my_addr, sizeof(my_addr)) == -1) {
							encontrada = 0;
						} else {
							uint8_t resHS = hacerHandShake(hsockAux);
							if (resHS == 0) {
								poolConexiones[i].enUso = 1;
								poolConexiones[i].descriptor = hsockAux;
								*descriptor = hsockAux;
								encontrada = 1;
							} else {
								encontrada = 0;
							}
						}
					}
				}
			} else if (i < maxConexiones && poolConexiones[i].enUso == 0) {
				poolConexiones[i].enUso = 1;
				*descriptor = poolConexiones[i].descriptor;
				encontrada = 1;
			}
			pthread_mutex_unlock(&mutexUsandoPoolConexiones);
		}
	}
}

void habilitarConexion(int32_t descriptor) {
	pthread_mutex_lock(&mutexUsandoPoolConexiones);
	uint8_t i;
	for (i = 0; i < maxConexiones && poolConexiones[i].descriptor != descriptor; i++)
		;
	if (i < maxConexiones && poolConexiones[i].descriptor == descriptor) {
		poolConexiones[i].enUso = 0;
	}
	pthread_mutex_unlock(&mutexUsandoPoolConexiones);
}

void cerrarConexion(int32_t descriptor) {
	pthread_mutex_lock(&mutexUsandoPoolConexiones);
	uint8_t i;
	for (i = 0; i < maxConexiones && poolConexiones[i].descriptor != descriptor; i++)
		;
	if (i < maxConexiones && poolConexiones[i].descriptor == descriptor) {
		close(descriptor);
		poolConexiones[i].enUso = -1;
	}
	pthread_mutex_unlock(&mutexUsandoPoolConexiones);
}

void cerrarConexiones() {
	pthread_mutex_lock(&mutexUsandoPoolConexiones);
	uint8_t i;
	for (i = 0; i < maxConexiones; i++) {
		if (poolConexiones[i].enUso != -1) {
			close(poolConexiones[i].descriptor);
			poolConexiones[i].enUso = -1;
		}
	}
	pthread_mutex_unlock(&mutexUsandoPoolConexiones);
}

/*int32_t enviar(int32_t descriptor, paquete_t paquete) {
 //PUEDE HABER ERRORES AL SER DISTINTO EL PLENGTH DEL TAMANIO REAL DEL PAYLOAD
 if (paquete.pLength <= MAX_PAYLOAD_LENGTH) {
 char* envio = serializar(paquete);
 int32_t res = send(descriptor, envio, paquete.pLength + 3, 0);
 free(envio);
 return res;
 } else {
 div_t division = div(paquete.pLength, MAX_PAYLOAD_LENGTH);
 uint64_t cantEnviosEnteros = division.quot;
 int32_t res = 0;
 uint8_t enviados;
 for (enviados = 0; res != -1 && enviados <= cantEnviosEnteros; enviados++) {
 paquete_t paqueteAux;
 paqueteAux.tipo = paquete.tipo;
 paqueteAux.pLength = paquete.pLength;
 paqueteAux.payload = (char*) malloc(MAX_PAYLOAD_LENGTH);
 memcpy(paqueteAux.payload, paquete.payload + (enviados * MAX_PAYLOAD_LENGTH), MAX_PAYLOAD_LENGTH);
 char* envio = serializar(paqueteAux);
 free(paqueteAux.payload);
 res = send(descriptor, envio, MAX_PAYLOAD_LENGTH + 3, 0);
 free(envio);
 }
 if (res != -1 && division.rem != 0) {
 paquete_t paqueteAux;
 paqueteAux.tipo = paquete.tipo;
 paqueteAux.pLength = paquete.pLength;
 paqueteAux.payload = (char*) malloc(division.rem);
 memcpy(paqueteAux.payload, paquete.payload + (enviados * MAX_PAYLOAD_LENGTH), division.rem);
 char* envio = serializar(paqueteAux);
 free(paqueteAux.payload);
 res = send(descriptor, envio, division.rem + 3, 0);
 free(envio);
 }
 return res;
 }
 }*/

/*int32_t recibir(int32_t descriptor, paquete_t* paquete) {
 //PUEDE HABER ERRORES AL SER DISTINTO EL PLENGTH DEL TAMANIO REAL DEL PAYLOAD
 char* buffer = (char*) malloc(MAX_PAYLOAD_LENGTH + 3);
 memset(buffer, '\0', MAX_PAYLOAD_LENGTH + 3);
 int32_t res = recv(descriptor, buffer, MAX_PAYLOAD_LENGTH + 3, 0);
 if (res != -1) {
 paquete_t paqueteAux = deserializar(buffer);
 if (paqueteAux.pLength > MAX_PAYLOAD_LENGTH) {
 memcpy(paqueteAux.payload, buffer, MAX_PAYLOAD_LENGTH);
 div_t division = div(paqueteAux.pLength, MAX_PAYLOAD_LENGTH);
 uint64_t cantRecibos = division.quot;
 uint64_t recibidos;
 for (recibidos = 1; res != -1 && recibidos < cantRecibos; recibidos++) {
 memset(buffer, '\0', MAX_PAYLOAD_LENGTH + 3);
 res = recv(descriptor, buffer, MAX_PAYLOAD_LENGTH + 3, 0);
 memcpy(paqueteAux.payload + (MAX_PAYLOAD_LENGTH * recibidos), buffer, MAX_PAYLOAD_LENGTH);
 }
 if (res != -1 && division.rem != 0) {
 memset(buffer, '\0', MAX_PAYLOAD_LENGTH + 3);
 res = recv(descriptor, buffer, MAX_PAYLOAD_LENGTH + 3, 0);
 memcpy(paqueteAux.payload + (MAX_PAYLOAD_LENGTH * recibidos), buffer, division.rem);
 }
 }
 if (res != -1) {
 paquete->tipo = paqueteAux.tipo;
 paquete->pLength = paqueteAux.pLength;
 paquete->payload = paqueteAux.payload;
 }
 }
 free(buffer);
 return res;
 }*/

uint8_t unPedidoUnRecibo(char* envio, char** recibo) {
	int32_t descriptor;
	obtenerConexion(&descriptor);
	int32_t resSend = send(descriptor, envio, strlen(envio), 0);
	if (resSend != -1) {
		char* headerRecv = (char*) malloc(3);
		int32_t resRecv = recv(descriptor, headerRecv, 3, MSG_PEEK);
		if (resRecv != -1) {
			uint16_t tamanioRecv;
			memcpy(&tamanioRecv, headerRecv + 1, 2);
			tamanioRecv += 3;
			free(headerRecv);
			char* reciboAux = (char*) malloc(tamanioRecv);
			memset(reciboAux, '\0', tamanioRecv);
			resRecv = recv(descriptor, reciboAux, tamanioRecv, 0);
			if (resRecv != -1) {
				*recibo = (char*) malloc(resRecv);
				memcpy(*recibo, reciboAux, resRecv);
				habilitarConexion(descriptor);
				free(reciboAux);
				return 0;
			} else {
				cerrarConexion(descriptor);
				free(reciboAux);
				return 1;
			}
		} else {
			cerrarConexion(descriptor);
			free(headerRecv);
			return 1;
		}
	} else {
		cerrarConexion(descriptor);
		return 2;
	}
}

/*uint8_t nPedidosNRecibos(paquete_t envio, paquete_t* recibo) {
 int32_t descriptor;
 obtenerConexion(&descriptor);
 int32_t resEnvio = enviar(descriptor, envio);
 if (resEnvio != -1) {
 int32_t resRecibir = recibir(descriptor, recibo);
 if (resRecibir != -1) {
 habilitarConexion(descriptor);
 return 0;
 } else {
 habilitarConexion(descriptor);
 return 1;
 }
 } else {
 habilitarConexion(descriptor);
 return 2;
 }
 }*/

uint8_t hacerHandShake(int32_t descriptor) {
	paquete_t paqueteEnvio;
	paqueteEnvio.tipo = TIPO_HANDSHAKE;
	paqueteEnvio.pLength = 0;
	paqueteEnvio.payload = NULL;
	char* envio = serializar(paqueteEnvio);
	int32_t resSend = send(descriptor, envio, strlen(envio), 0);
	free(envio);
	if (resSend != -1) {
		char* headerRecv = (char*) malloc(3);
		int32_t resRecv = recv(descriptor, headerRecv, 3, MSG_PEEK);
		if (resRecv != -1) {
			uint16_t tamanioRecv;
			memcpy(&tamanioRecv, headerRecv + 1, 2);
			tamanioRecv += 3;
			free(headerRecv);
			char* reciboAux = (char*) malloc(tamanioRecv);
			memset(reciboAux, '\0', tamanioRecv);
			resRecv = recv(descriptor, reciboAux, tamanioRecv, 0);
			if (resRecv != -1) {
				paquete_t paqueteRecibo = deserializar(reciboAux);
				free(reciboAux);
				if (paqueteRecibo.tipo == 0 && paqueteRecibo.pLength == 0) {
					return 0;
				} else {
					return 1;
				}
			} else {
				free(reciboAux);
				return 2;
			}
		} else {
			cerrarConexion(descriptor);
			free(headerRecv);
			return 1;
		}
	} else {
		return 3;
	}
	return 0;
}

void servidor(void(*procPed)(char*, char**, uint16_t*)) {
	struct sockaddr_in my_addr;
	uint32_t hsock;
	uint32_t *p_int;

	socklen_t addr_size = 0;
	uint32_t *csock;
	struct sockaddr_in sadr;
	pthread_t thread_id = 0;

	hsock = socket(AF_INET, SOCK_STREAM, 0);
	if (hsock == -1) {
		printf("Error en el socket() \n");
	} else {
		p_int = (uint32_t*) malloc(sizeof(uint32_t));
		*p_int = 1;

		if ((setsockopt(hsock, SOL_SOCKET, SO_REUSEADDR, (char*) p_int, sizeof(uint32_t)) == -1) || (setsockopt(hsock, SOL_SOCKET, SO_KEEPALIVE, (char*) p_int, sizeof(uint32_t)) == -1)) {
			printf("Error en el setsockopt() \n");
		} else {
			free(p_int);

			my_addr.sin_family = AF_INET;
			my_addr.sin_port = htons(puerto);

			memset(&(my_addr.sin_zero), 0, 8);
			my_addr.sin_addr.s_addr = INADDR_ANY;

			if (bind(hsock, (struct sockaddr*) &my_addr, sizeof(my_addr)) == -1) {
				printf("Error en el bind() \n");
			} else {

				if (listen(hsock, maxConexiones) == -1) {
					printf("Error en el listen() \n");
				} else {

					addr_size = sizeof(struct sockaddr_in);

					while (1) {
						printf("Esperando conexión...\n");
						csock = (uint32_t*) malloc(sizeof(uint32_t));
						if ((*csock = accept(hsock, (struct sockaddr*) &sadr, &addr_size)) != -1) {
							printf("\nConexión recibida desde %s \n", inet_ntoa(sadr.sin_addr));

							t_infoAtenderCliente infoAtenderCliente;
							infoAtenderCliente.descriptor = *csock;
							infoAtenderCliente.procPed = procPed;

							pthread_create(&thread_id, NULL, (void*) &atenderCliente, (void*) &infoAtenderCliente);
							apilarThread(&pilaThreads, thread_id);
						} else {
							printf("Error en el accept()\n");
						}
					}
				}
			}
		}
	}
}

void atenderCliente(void *infoAtenderCliente) {

	t_infoAtenderCliente infoAtenderClienteAux = *((t_infoAtenderCliente*) infoAtenderCliente);
	uint8_t terminarAtencion = 0;

	while (!terminarAtencion) {
		char* headerRecv = (char*) malloc(3);
		int32_t resRecv = recv(infoAtenderClienteAux.descriptor, headerRecv, 3, MSG_PEEK);
		if (resRecv != -1) {
			uint16_t tamanioRecv;
			memcpy(&tamanioRecv, headerRecv + 1, 2);
			tamanioRecv += 3;
			char *bufferRecv = (char*) malloc(tamanioRecv);
			memset(bufferRecv, '\0', tamanioRecv);
			char* bufferSend = NULL;

			if (recv(infoAtenderClienteAux.descriptor, bufferRecv, tamanioRecv, 0) != -1) {
				uint16_t tamanioSend;

				infoAtenderClienteAux.procPed(bufferRecv, &bufferSend, &tamanioSend);

				if (send(infoAtenderClienteAux.descriptor, bufferSend, tamanioSend, 0) == -1) {
					terminarAtencion = 1;
				}
			} else {
				terminarAtencion = 1;
			}
			free(bufferRecv);
			free(bufferSend);
		} else {
			terminarAtencion = 1;
		}
		free(headerRecv);
	}

	close(infoAtenderClienteAux.descriptor);
}

